/**
 * 表单验证插件
 * Author: 陆楚良
 * Version: 2.0.0
 * Date: 2014/11/28
 * QQ: 519998338
 *
 * https://git.oschina.net/luchg/jquery.CL_Valitator.js.git
 *
 * License: http://www.apache.org/licenses/LICENSE-2.0
 */

/*
首先，咱们先规范一下词语：
$(".J_form").CL_Valitator({
	......
	rules： {......}
})
$(".J_form")选择器选中的节点，我们称呼为：【顶点】
函数里的参数，我们称呼为：【顶点配置】
【顶点配置】中的rules我们称呼为【元素规则字典】

$(".J_form").CL_Valitator(".J_username", {......})
第一个参数".J_username"选择器选中的节点，我们称呼为：【元素】
第二个参数我们称呼为：【元素配置】

$(".J_form").CL_Valitator(".J_username", [......])
第二个参数与上文的区别在于它是一个数组而非对象，我们称呼为：【规则组】
事实上，【规则组】是元素配置的一个项

$(".J_form").CL_Valitator(".J_username", [
	{......}
]);
【规则组】中的对象，我们称呼为：【规则】


配置参考
{
	// 废弃1.0版本的attr设置
	onerror		: null,			// 可选，缺省null，错误时触发，接收到错误信息参数，this为引发错误的【元素】
	onsuccess	: null,			// 可选，缺省null，正确时触发，接收到正确信息参数，this为引发错误的【元素】，单个【元素】一次验证不管有多少条【规则】，最终它只触发一次
	onlock 		: null,			// 可选，缺省null，锁定时触发，this为【顶点】
	onunlock 	: null,			// 可选，缺省null，解锁时触发，this为【顶点】
	rules : {				// 选填，【元素规则字典】(可通过添加、修改方法设置)
		 ".J_username":[		// 必填，2.0版本该值不再是attr属性值，而是一个jQ选择器，注：该选择器只查找当前表单旗下子元素
		 						// 		其值为一个数组或对象(对象将支持设置针对该表单的公共error、success、onlock、onunlock、onerror、onsuccess)
			{
				type    : "RegExp",							// 必填，验证属性，这里是正则，支持regexp、length、minlength、maxlength、callback、ajax，不区分大小写
				action  : "email",							// 必填，根据属性响应不同操作，如RegExp正则，相应的action就应该是一个正则表达式对象，
															//		当然本插件内置了几个常见的正则式，用字符串对象可使用内置正则式，内置正则式如下：
															//		require(不为空)、email(邮箱)、url(链接地址)、currency(货币)、number(数字)、
															//		zip(邮编)、integer(整数)、pInteger(正整数)、double(浮点数)、pDouble(正浮点数)、
															//		english(英文字母)、chinese(汉子)，不区分大小写

				match	: true,								// 可选，缺省true，正则验证独有，布尔值，设置判断匹配的结果，规则如下：
															// 		当为true时， 正则匹配通过时为正确，不通过时为错误；
															// 		当为false时，正则匹配通过时为错误，不通过时为正确。

				error   : "请使用邮箱地址作为用户名",		// 可选，缺省null，错误时提示的错误信息
				success : "用户名可以使用",					// 可选，缺省null，正确时提示的正确信息，当存在多个验证规则时，设置多个成功信息的话，后面的会取代前面的
				onerror : null,								// 可选，缺省null，错误时触发，接收到错误信息参数，this为引发错误的表单
				onsuccess:null,								// 可选，缺省null，正确时触发，每次正确都会触发，接收到正确信息参数，this为引发错误的表单
			},
			{
				type    : "length",							// 必填，长度验证
				action  : [5,255],							// 必填，数组，[最小长度,最大长度]
				lengthAt: false,							// 可选，缺省false，长度验证和最大、最小长度验证拥有，布尔值，设置是否以字节为单位
				error   : "邮箱地址字符数限制在...",
				success : "用户名可以使用",
				onerror : null,
				onsuccess:null,
			},
			{
				type    : "minLength",						// 必填，最小长度验证
				action  : 5,								// 必填，整型，最小长度
				lengthAt: false,							// 可选，缺省false，长度验证和最大、最小长度验证拥有，布尔值，设置是否以字节为单位
				error   : "邮箱地址字符数限制在...",
				success : "用户名可以使用",
				onerror : null,
				onsuccess:null,
			},
			{
				type    : "maxLength",					// 必填，最大长度验证
				action  : 255,							// 必填，整型，最大长度
				lengthAt: false,						// 可选，缺省false，长度验证和最大、最小长度验证拥有，布尔值，设置是否以字节为单位
				error   : "邮箱地址字符数限制在...",
				success : "用户名可以使用",
				onerror : null,
				onsuccess:null,
			},
			{
				type    : "callback",					// 必填，函数验证
				action  : function(value, callback){	// 必填，函数，必须接收一个value和一个callback，this为触发验证的表单，
														//		value为该表单的value，callback则是用于处理正确与否的回调函数

					callback(false, "自定义错误信息");	// 必须执行，回调函数只接受两个参数，第一个参数仅支持true或false，表示正确与否，
														// 		第二个参数为可缺省参数，用于自定义正确或错误信息，当缺省时，使用设置的error或success
				},
				error   : "请使用邮箱地址作为用户名",
				success : "用户名可以使用",
				onerror : null,
				onsuccess:null,
			},
			{
				type    : "ajax",						// 必填，ajax验证
				action  : "http://www.xxxx.com",		// 必填，请求地址
				method 	: "post",						// 可选，缺省get，请求方法
				name 	: false,						// 可选，缺省则使用前面设置的attr属性的值作为字段名，字符串，发送到服务器的字段名，
				data 	: {},							// 可选，缺省值为一个空对象，发送到服务器的额外数据
				error   : "请使用邮箱地址作为用户名",
				success : "用户名可以使用",
				net_error: "网络连接失败",				// 可选，缺省null，ajax验证独有，网络错误时的错误信息
				onbefore  : null,						// 可选，缺省null，ajax验证独有，函数对象，ajax请求前触发，this为触发请求的表单
				oncomplete: null,						// 可选，缺省null，ajax验证独有，函数对象，自定义数据解析，
														//		将接收到来自服务器返回的一串字符串，正确与错误的触发与callback的action相同
														// 		可缺省，当缺省时则使用默认的方法，即当服务器返回true(不区分大小写)时认为正确，否则一律认为错误
				onerror : null,
				onsuccess:null,
			}
		],

		".J_email" : {	// 当为对象时，可以更全面地扩展，将支持缺省参数设置、锁定解锁事件、失败与成功总事件
			success : "邮箱可以使用",	// 可选，缺省null，设置针对email的缺省成功信息
			error   : null,				// 可选，缺省null，设置针对email的缺省错误信息
			onlock  : null,				// 可选，缺省null，针对email表单的锁定事件
			onunlock: null,				// 可选，缺省null，针对email表单的解锁事件
			onerror : null,				// 可选，缺省null，针对email表单的总错误事件（非缺省事件设置）
			onsuccess: null,			// 可选，缺省null，针对email表单的总成功事件（非缺省事件设置），email一次验证不管有多少条规则，它仅触发一次
			rules : [					// 必填，当email验证为对象时候，规则同理以rules写在对象里面
				{
					type  : "maxlength",
					error : "您的电子邮箱过长了请换另一个,只能在100个字符以内。",
					action: 100
				},
				{
					type  : "regexp",
					error : "您填写的不是一个有效的电子邮件地址，请输入一个有效的电子邮件地址。",
					action: "email"
				}
			]
		}
	}
};
*/

!function($, undefined){

	if(window.Queue==undefined){	// 本插件需要依赖Queue队列执行引擎
		console && console.error("缺少Queue队列引擎！");
		return;
	}
	var data_config = "cl_valitator_config";
	var data_config_id = "cl_valitator_config_id";
	// 以字节为单位计算字符串长度
	var lengthAt = function(strTemp){
		    var i, sum;
		    sum = 0;
		    for (i = 0; i < strTemp.length; i++) {
		        if ((strTemp.charCodeAt(i) >= 0) && (strTemp.charCodeAt(i) <= 255))
		            sum = sum + 1;
		        else
		            sum = sum + 2;
		    }
		    return sum
		};

	var RegExps={
			"require"	: /\S+/,	// 不为空
			"email" 	: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,		// 邮箱
			"url"		: /^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/,	// 链接地址
			"currency"	: /^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$/,	// 货币
			"number"	: /^\d+$/,								// 数字
			"zip"		: /^\d{6}$/,							// 邮编
			"integer"	: /^(0|-?[1-9][0-9]*)$/,				// 整数
			"pinteger"	: /^(0|[1-9][0-9]*)$/,					// 正整数
			"double"	: /^-?(0|[1-9][0-9]*)\.[0-9]+$/,		// 浮点数
			"pdouble"	: /^(0|[1-9][0-9]*)\.[0-9]+$/,			// 正浮点数
			"english"	: /^[A-Za-z]+$/,						// 英文字母
			"chinese"	: /^[\u4e00-\u9fa5]+$/					// 汉字
		};

	var isObj = function(e){return $.type(e)=="object"};
	var isArr = function(e){return $.type(e)=="array" };
	var isStr = function(e){return $.type(e)=="string"};
	var isNum = function(e){return $.type(e)=="number"};
	var isFun = function(e){return $.type(e)=="function"};
	var isReg = function(e){return $.type(e)=="regexp"};

	// 取唯一ID
	var newID = (function(){
		var id=0;
		return function(){return id++}
	})();



	$.fn.CL_Valitator = function(c, r){
		if(isFun(c)){
			// 当第一个参数为函数时候认为是触发验证操作
			this.each(function(){
				var C = $(this).data(data_config);
				$.each(C._func, function(k,v){
					v.focus.call(v.self);
					v.blur.call(v.self, c);
				});
			});
		}else if(r===undefined){
			// 大设置
			c = $.extend({
				onerror		: null,			// 可选，错误时触发，接收到错误信息参数，this为引发错误的表单
				onsuccess	: null,			// 可选，正确时触发，接收到正确信息参数，this为引发错误的表单，单个表单一次验证不管有多少条规则，最终它只触发一次
				onlock 		: null,			// 可选，锁定时触发，this为选择器选择的DOM
				onunlock 	: null,			// 可选，解锁时触发，this为选择器选择的DOM
				onfocus 	: null,			// 可选，获得焦点时触发，this为获得焦点的表单
			}, isObj(c) ? c : {} );
			this.each(function(){
				var $this = $(this), config = $.extend({}, c);
				config._lock = false;
				config._dict = {};
				config._func = {};
				$this.data(data_config, config);	// 存TOP设置
				isObj(config.rules) && $.each(config.rules, function(k,v){
					$this.CL_Valitator(k,v);		// 遍历存表单设置
				});
			});

		}else{
			this.each(function(){
				// 添加、修改规则
				var top = this;
				var $top = $(top);

				isObj($top.data(data_config)) || $top.CL_Valitator({});
				var C = $top.data(data_config);

				$top.find(c).each(function(){
					var self = this;
					var $self = $(self);
					var indexOf = $.inArray(self, C._dict);
					var conf;
					var ID = $self.data(data_config_id);	// ID用作唯一标识
					switch($.type(r)){
						case "array":
							conf = isNum(ID)?C._dict[ID]:{};
							conf.rules = r;
							break;
						case "object":
							conf = $.extend(isNum(ID)?C._dict[ID]:{}, r);
							conf.rules = isArr(conf.rules) ? conf.rules : [];
							break;
						case "null":
							conf = {rules:[],_lock:false};
							break;
					}
					if(conf){
						if(!isNum(ID)){
							var ID2 = newID();
							$self.data(data_config_id, ID2);
							C._dict[ID2] = conf;
						}else{
							C._dict[ID] = conf;
						}

						// 解析规则
						var rules=conf.rules,f = [],i;		// f用于存放验证规则处理函数
						/* 
							原理：
								解析验证规则，封装为一个函数，存储到f列表中，再在表单获得焦点时标记锁定，触发全局锁定事件，
						 		然后在表单失去焦点时，将f列表添加到队列依次执行，如果验证失败则结束验证，并触发顶级错误事件，
						 		如果验证成功则继续下一条验证规则，如果全部验证成功，表示表单输入正确，标志解锁，进行全部表单遍历，
						 		如果全部已解锁，则触发顶级成功事件与解锁事件
						 */

						var success   = conf.success 	|| null,
							error     = conf.error 	|| null;
						for(i=0;i<rules.length;i++){	// 遍历规则
							switch(rules[i].type.toLowerCase()){	// 使用效率较高的switch解析规则
								case "regexp":
									// 正则验证
									f[f.length] = (function(r){		// 采用闭包让变量命名更自由
										var a = isStr(r.action) ? RegExps[r.action.toLowerCase()] : r.action,	// 读取验证操作，如果是字符串，尝试读取内置正则
											m = r.match===false ? false : true;		// 读取匹配结果验证，如果为true，则匹配通过为正确，反之如果为false，则匹配不通过为正确
										return function(callback){
												// 如果正则有效并且匹配通过，回调成功，否则回调失败
												(isReg(a) && a.test($self.val())==m) ? callback(r, true, r.success||success) : callback(r, false, r.error||error);
											};
									})(rules[i]);
									break;

								case "length":
									// 长度验证
									f[f.length] = (function(r){
										var a = r.action;	// 读取验证操作
										return function(callback){
												var l = r.lengthAt?lengthAt($self.val()):$self.val().length;	// 计算长度，如果设置以字节为单位，则以字节计算长度
												(l>=a[0] && l<=a[1]) ? callback(r, true, r.success||success) : callback(r, false, r.error||error);
											};
									})(rules[i]);
									break;

								case "minlength":
									// 最小长度验证
									f[f.length] = (function(r){
										return function(callback){
												var l = r.lengthAt?lengthAt($self.val()):$self.val().length;
												(l>=r.action) ? callback(r, true, r.success||success) : callback(r, false, r.error||error);
											};
									})(rules[i]);
									break;

								case "maxlength":
									// 最大长度验证
									f[f.length] = (function(r){
										return function(callback){
												var l = r.lengthAt?lengthAt($self.val()):$self.val().length;
												(l<=r.action) ? callback(r, true, r.success||success) : callback(r, false, r.error||error);
											};
									})(rules[i]);
									break;

								case "callback":
									// 函数验证
									f[f.length] = (function(r){
										return function(callback){
												r.action.call(self, $self.val(), function(status,msg){	// 执行用户配置的回调函数，并给其传递回调解析
													// 解析用户回调
													status ? callback(r, true, msg||(r.success||success)) : callback(r, false, msg||(r.error||error));
												});
											};
									})(rules[i]);
									break;

								case "ajax":
									// ajax验证
									f[f.length] = (function(r){
										var data = $.param(r.data||{});		// 解析存储额外数据（避免因引用导致参数发生变化）
										return function(callback){
												isFun(r.onbefore) && r.onbefore.call(self);		// 触发请求前事件
												$.ajax({			// ajax请求开始
													url: r.action,
													data: data+"&"+(r.name||$self.attr("name"))+"="+encodeURIComponent($self.val()),	// 拼接请求数据，额外数据+&字段名=值
													type: r.method||'get',	// 缺省get请求
													error: function(){
														callback(r, false, r.net_error);	// 网络错误回调
													},
													success:function(data){
														if(isFun(r.oncomplete)){	// 如果用户配置了自定义数据解析，则使用用户自定义的解析回调
															r.oncomplete.call(self, data, function(status,msg){
																// 解析用户回调的数据
																status ? callback(r, true, msg||(r.success||success)) : callback(r, false, msg||(r.error||error))
															});
														}
														else{
															// 如果用户没有配置自定义数据解析，则使用默认的，如果为true，则认为正确，否则错误，不区分大小写
															$.trim(data).toUpperCase()=="TRUE" ? callback(r, true, r.success||success) : callback(r, false, r.error||error);
														}
													}
												});
											};
									})(rules[i]);
									break;
							}
						}
						conf._f = f;

						// 事件绑定
						if(!isNum(ID)){
							var Q = new Queue();		// 创建队列
							C._func[ID2] = {
								self:self,
								focus:function(){		// 获得焦点事件
									var C = $top.data(data_config);		// 存储全局设置，（以防中途修改，因此需要在事件里读取）
									if(isObj(C)){
										var R = C._dict[$self.data(data_config_id)];	// 存储当前规则，（以防中途修改，因此需要在事件里读取）
										if(R){
											Q.clear();							// 清空队列，避免用户频繁操作引发的一系列问题
											R._lock = true;						// 锁定当前表单
											isFun(R.onlock) && R.onlock.call(self);		// 触发表单锁定
											isFun(C.onlock) && C.onlock.call(top);		// 触发全局锁定
											isFun(R.onfocus)&& R.onfocus.call(self);	// 触发表单焦点事件
											isFun(C.onfocus)&& C.onfocus.call(self);	// 触发全局焦点事件
										}
									}
								},
								blur:function(callback){
									var C = $top.data(data_config);		// 存储全局设置，（以防中途修改，因此需要在事件里读取）
									if(isObj(C)){
										var R = C._dict[$self.data(data_config_id)];	// 存储当前规则，（以防中途修改，因此需要在事件里读取）
										if(R){
											Q.clear();			// 清除队列，避免用户频繁操作引发的一系列问题
											var success_msg = null;			// 存储成功信息（多条条件的成功信息，缺省只使用配置的最后一条）
											var unlock = function(){
												if(isFun(C.onunlock) || isFun(callback)){
													var p=true;
													$.each(C._dict, function(k,v){
														if(v._lock!==false){
															p=false;
														}
													});
													if(p){
														isFun(C.onunlock) && C.onunlock.call(top);
														isFun(callback) && callback.call(top);
													}
												}
											};
											if(R._f.length==0){
												isFun(R.onsuccess) && R.onsuccess.call(self, success_msg);	// 触发表单成功事件
												isFun(C.onsuccess) && C.onsuccess.call(self, success_msg);	// 触发顶级成功事件
												R._lock = false;											// 解锁当前表单
												isFun(R.onunlock) && R.onunlock.call(self);					// 触发表单解锁事件
												unlock();	// 解析所有表单是否全部解锁
											}else{
												for(var i=0; i<R._f.length; i++){	// 遍历验证方法
													!function(i){					// 使用闭包解决i在回调下引发的问题
														Q.queue(function(){			// 添加队列
															R._f[i](function(r, status, msg){	// 执行验证
																// C为顶部配置， R为表单配置， r为当前规则配置
																if(status){		// 验证成功
																	success_msg = msg||success_msg;	// 成功信息赋值
																	isFun(r.onsuccess) && r.onsuccess.call(self, msg);				// 触发验证规则的成功事件
																	if(i==R._f.length-1){											// 如果是最后一条验证方法
																		isFun(R.onsuccess) && R.onsuccess.call(self, success_msg);	// 触发表单成功事件
																		isFun(C.onsuccess) && C.onsuccess.call(self, success_msg);	// 触发顶级成功事件
																		R._lock = false;											// 解锁当前表单
																		isFun(R.onunlock) && R.onunlock.call(self);					// 触发表单解锁事件
																		unlock();	// 解析所有表单是否全部解锁
																	}
																	Q.dequeue();	// 释放队列
																}else{
																	Q.clear();		// 如果失败，清空队列
																	isFun(r.onerror) && r.onerror.call(self, msg);		// 触发验证规则的错误事件
																	isFun(R.onerror) && R.onerror.call(self, msg);		// 触发表单错误事件
																	isFun(C.onerror) && C.onerror.call(self, msg);		// 触发顶级错误事件
																	isFun(R.onlock)  && R.onlock.call(self);			// 触发表单锁定事件
																	isFun(C.onlock)  && C.onlock.call(top);				// 触发全局锁定事件
																}
															});
														});
													}(i);
												}
											}
										}
									}
								}
							};
							$self.focus(C._func[ID2].focus).blur(C._func[ID2].blur);
						}
						f.length==0 && $self.blur();	// 当重写清空验证时候，触发验证使得全局解锁事件可以及时触发
					}
				});
			});
		}

		return this;
	}





}(jQuery);